package org.pada.domain.config.provider;

import com.fasterxml.jackson.databind.JsonNode;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.pada.client.dto.Resp;
import org.pada.client.exception.BaseException;
import org.pada.client.exception.SystemErrorCode;
import org.pada.client.util.JsonUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author shen.peng
 * @since 2024/9/12
 */
@Slf4j
@Component
public class RpcRouter implements ApplicationContextAware {

    public static final Map<String, Method> METHOD_MAP = new HashMap<>();

    public static final Map<String, Object> SERVICE_MAP = new HashMap<>();

    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        Set<Class<?>> rpcServiceList = RpcProviderScanRegister.SERVICE_CLASS_SET;
        rpcServiceList.forEach(x -> {
            Class<?> iService = x.getInterfaces().length == 0 ? x : x.getInterfaces()[0];
            String name = iService.getName();
            SERVICE_MAP.put(name, applicationContext.getBean(iService));
            Arrays.stream(x.getDeclaredMethods()).forEach(t -> {
                METHOD_MAP.put(name + "#" + t.getName(), t);
            });
        });
    }

    public @ResponseBody Object route(@RequestBody RpcBody rpcBody, HttpServletRequest request) {
        String service = rpcBody.getService();
        String methodName = rpcBody.getMethod();
        JsonNode body = rpcBody.getBody();
        Map<String, Object> header = rpcBody.getHeader();
        if (!CollectionUtils.isEmpty(header)) {
            header.forEach(request::setAttribute);
        }
        String methodId = String.format("%s#%s", service, methodName);
        Object o = SERVICE_MAP.get(service);
        if (Objects.isNull(o)) {
            return Resp.buildFailure(SystemErrorCode.UNKNOWN_SERVICE);
        }
        Method method = METHOD_MAP.get(methodId);
        if (Objects.isNull(method)) {
            return Resp.buildFailure(SystemErrorCode.UNKNOWN_METHOD);
        }
        log.info("provider url {} request {} ", request.getRequestURI(), JsonUtil.toJSONString(rpcBody));
        Object invoke = invoke(body, o, method);
        log.info("provider url {} response {} ", request.getRequestURI(), JsonUtil.toJSONString(invoke));
        return invoke;
    }

    static Object invoke(JsonNode body, Object o, Method method) {
        try {
            Class<?> parameterType = method.getParameterTypes()[0];
            Object param = JsonUtil.convertObject(body, parameterType);
            return method.invoke(o, param);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause instanceof BaseException) {
                log.error("invoke service method fail", cause);
                return Resp.buildFailure(((BaseException)cause).getErrCode(), ((BaseException)cause).getErrMsg());
            }
            log.error("invoke service method fail", e);
            return Resp.buildFailure(SystemErrorCode.UNKNOWN_SYSTEM_ERR);
        } catch (Throwable e) {
            log.error("invoke service method fail", e);
            return Resp.buildFailure(SystemErrorCode.UNKNOWN_SYSTEM_ERR);
        }
    }

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
